package com.spider.im.common.aspect;

import cn.hutool.core.util.StrUtil;
import com.spider.im.common.annotation.RedisLock;
import com.spider.im.common.constant.RedisKey;
import com.spider.im.common.constant.RespCodeEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * Redis 分布式锁切面类，用于通过 AOP 拦截带有 @DistributedLock 注解的方法，
 * 并在执行前加锁，防止分布式环境下并发问题。
 */
@Slf4j
@Aspect
@Component
public class RedisLockAspect {

    // Redisson 客户端，用于获取分布式锁
    @Resource
    private RedissonClient redissonClient;

    // 缓存 SpEL 表达式，避免重复解析
    private final Map<AnnotatedElementKey, Expression[]> expressionsCache = new ConcurrentHashMap<>();

    /**
     * 环绕通知：拦截所有标注了 @RedisLock 的方法，为其添加分布式锁逻辑。
     * 敏感参数加锁，可以加上使用MD5加密
     *   @RedisLock(keys = {"T(cn.hutool.crypto.digest.DigestUtil).md5Hex(#phoneNumber)"})
     *
     * @param joinPoint 切点对象，包含目标方法的信息和参数
     * @return 方法执行结果
     * @throws Throwable 可能抛出的异常
     */
    @Around("@annotation(com.spider.im.common.annotation.RedisLock)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 获取方法上的 @DistributedLock 注解实例
        RedisLock annotation = method.getAnnotation(RedisLock.class);
        String[] spElKeys = annotation.keys();  // 获取 SpEL 表达式定义的 key

        // 构建 EvaluationContext，用于 SpEL 表达式求值
        EvaluationContext context = createEvaluationContext(method, joinPoint.getArgs(), signature.getParameterNames());
        AnnotatedElementKey elementKey = new AnnotatedElementKey(method, method.getDeclaringClass());

        // 获取或缓存 SpEL 表达式对象
        Expression[] expressions = getExpressions(spElKeys, elementKey);
        StringBuilder lockKeyBuilder = new StringBuilder("redis_lock:").append(method.getName()).append(":");
        for (Expression expression : expressions) {
            String keyPart = expression.getValue(context, String.class);
            if (StrUtil.isNotBlank(keyPart)) {
                lockKeyBuilder.append(keyPart).append("_");
            }
        }

        if (lockKeyBuilder.charAt(lockKeyBuilder.length() - 1) == '_') {
            lockKeyBuilder.deleteCharAt(lockKeyBuilder.length() - 1);
        }
        String redisKey = lockKeyBuilder.toString();
        log.info("获取锁：redisKey {}", redisKey);
        RLock lock = redissonClient.getLock(redisKey);  // 获取 Redisson 锁对象

        try {
            // 尝试获取锁
            boolean isLocked = lock.tryLock(annotation.waitTime(), annotation.leaseTime(), annotation.timeUnit());
            if (!isLocked) {
                log.warn("获取锁失败：redisKey {}", redisKey);
                throw new BusinessException(RespCodeEnum.REDIS_LOCK);
            }
            // 执行业务方法
            return joinPoint.proceed();
        } finally {
            // 确保当前线程持有锁时才解锁，避免误释放其他线程持有的锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 获取并缓存 SpEL 表达式对象。使用双重检查锁定确保线程安全。
     *
     * @param keys       SpEL 表达式字符串
     * @param elementKey 缓存键，由方法和声明类组成
     * @return 解析后的表达式对象
     */
    private Expression[] getExpressions(String[] keys, AnnotatedElementKey elementKey) {
        Expression[] expressions = expressionsCache.get(elementKey);
        if (expressions == null) {
            synchronized (expressionsCache) {
                expressions = expressionsCache.get(elementKey);
                if (expressions == null) {
                    expressions = Arrays.stream(keys)
                            .map(new SpelExpressionParser()::parseExpression)
                            .toArray(Expression[]::new);
                    expressionsCache.put(elementKey, expressions);
                }
            }
        }
        return expressions;
    }

    /**
     * 创建 SpEL 表达式求值上下文，将方法参数注入到变量中。
     *
     * @param method 当前方法
     * @param args   方法参数数组
     * @param paramNames 参数名称数组
     * @return 构建好的 EvaluationContext 实例
     */
    private EvaluationContext createEvaluationContext(Method method, Object[] args, String[] paramNames) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < args.length; i++) {
            // 将每个参数绑定到上下文中，变量名为实际参数名
            context.setVariable(paramNames[i], args[i]);
        }
        return context;
    }
}
